﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;
using NLog;

namespace DEFLib
{
	public static class FolderDeleter
	{
		private static Logger logger = LogManager.GetCurrentClassLogger();

		public static IEnumerable<string> GetDirsToDelete(string ignoreFilesList, string baseDir)
		{
			string[] ignoreFiles = getIgnoreFiles(ignoreFilesList);
			return GetDirsToDelete(ignoreFiles, new string[0], baseDir);
		}

		public static IEnumerable<string> GetDirsToDelete(string[] ignoreFiles, string[] ignoreDirs, string baseDir)
		{
			if (!Directory.Exists(baseDir))
			{
				throw new ApplicationException(string.Format("Error.  Folder: '{0}' not found.", baseDir));
			}
			ignoreFiles = regexifyGlobs(ignoreFiles);
			ignoreDirs = regexifyGlobs(ignoreDirs);
			HashSet<string> dirsToDelete = new HashSet<string>();
			logger.Debug("Loading directories . . .");
			var dirs = Directory.GetDirectories(baseDir, "*", SearchOption.AllDirectories);
			Console.WriteLine("{0} directories found.", dirs.Count());
			foreach (string dir in dirs.OrderByDescending(d => d.Length))
			{
				logger.Debug("Processing directory '{0}' . . .", dir);
				if (isIgnored(dir, ignoreDirs, true))
				{
					//directory should be deleted because it is ignored
					dirsToDelete.Add(dir);
					continue;
				}
				var files = Directory.GetFiles(dir, "*.*", SearchOption.AllDirectories);
				files = files.Where(f => !(dirsToDelete.Any(d => f.IndexOf(d) > -1))).ToArray();

				if (files.Count() < 1 || !(files.Any(f => isIgnored(f, ignoreFiles, false) == false)))
				{
					dirsToDelete.Add(dir);
				}
			}

			return dirsToDelete;
		}

		public static IEnumerable<string> GetFolderFileDisposition(string[] ignoreFiles, string baseDir)
		{
			if (!Directory.Exists(baseDir))
			{
				throw new ApplicationException(string.Format("Error.  Folder: '{0}' not found.", baseDir));
			}
			ignoreFiles = regexifyGlobs(ignoreFiles);
			List<string> fileDispositions = new List<string>();
			List<string> dirs = new List<string>(Directory.GetDirectories(baseDir, "*", SearchOption.AllDirectories));
			dirs.Add(baseDir);
			foreach (string dir in dirs)
			{
				var files = Directory.GetFiles(dir, "*.*", SearchOption.AllDirectories);

				if (files.Count() > 0)
				{
					foreach (string file in files)
					{
						if (isIgnored(file, ignoreFiles, false))
						{
							fileDispositions.Add(string.Format("{0} (ignored)", file));
						}
						else
						{
							fileDispositions.Add(file);
						}
					}
				}
			}

			return fileDispositions.OrderBy(s => s);
		}

		public static void DeleteDirs(IEnumerable<string> dirsToDelete)
		{
			foreach (string dir in dirsToDelete.OrderBy(d => d.Length))
			{
				if (Directory.Exists(dir))
				{
					RecursiveDeleteFilesAndDirectories(dir);
				}
			}
		}

		public static bool RecursiveDeleteFilesAndDirectories(string baseDir)
		{
			bool result = false;

			string[] files = Directory.GetFiles(baseDir);
			string[] dirs = Directory.GetDirectories(baseDir);

			foreach (string file in files)
			{
				File.SetAttributes(file, FileAttributes.Normal);
				File.Delete(file);
			}

			foreach (string dir in dirs)
			{
				RecursiveDeleteFilesAndDirectories(dir);
			}

			Directory.Delete(baseDir, false);

			return result;
		}

		private static bool isIgnored(string name, string[] ignore, bool dirNameMatchesOnly)
		{
			foreach (string i in ignore)
			{
				string match = i;
				if (dirNameMatchesOnly)
				{
					match = string.Concat("\\", i, "$");
				}
				Regex re = RegexCache.Retrieve(match);
				if (re.IsMatch(name))
				{
					return true;
				}
			}

			return false;
		}

		private static string[] getIgnoreFiles(string filesList)
		{
			string[] retVal = filesList.Split(',');
			retVal = regexifyGlobs(retVal);
			return retVal;
		}

		private static string[] regexifyGlobs(string[] retVal)
		{
			retVal = retVal.Select(f => f.Replace(".", "\\.").Replace("?", ".").Replace("*", ".*?")).ToArray();
			return retVal;
		}
	}
}
